import * as cc from 'cc';
import { EDITOR } from 'cc/env';
export enum ChannelOf { Resource = 'Resource', Rms = 'Rms', Language = 'Language' };
export default class Gi {
    constructor() { return Gi['instance'] ??= this; }
    private static resChannel: string = '';
    private static resources: Object = cc.js.createMap(true);
    private static poolLists: Object = cc.js.createMap(true);
    private static rmsChannel: string = '';
    private static langChannel: string = '';
    private static languages: Object = cc.js.createMap(true);
    public log = EDITOR ? cc.log : console.log;
    public warn = EDITOR ? cc.warn : console.warn;
    public error = EDITOR ? cc.error : console.error;
    public alert(...data: any[]) { gi.log(`%c%s`, 'background:#000;color: #ff0;font-weight:bold;', ...data); }
    public rootNode: cc.Node = null;
    public findKey(obj: Object, value: any): any {
        for (let key in obj) { if (obj[key] === value) return key; }
        return null;
    }
    public scheduleOnce(callback: () => void, delay: number): number {
        return -setTimeout(callback, delay * 1000);
    }
    public schedule(callback: (remain: number) => void, interval: number, repeat: number = -1): number {
        callback(--repeat);
        if (repeat === 0) return 0;
        let handle = setInterval(() => { callback(--repeat); repeat === 0 && clearInterval(handle); }, interval * 1000);
        return handle;
    }
    public unschedule(handle: number) {
        handle > 0 ? clearInterval(handle) : clearTimeout(-handle);
    }
    /***************************资源管理***************************/
    public setChannel(channel: string, channelOf: ChannelOf = ChannelOf.Resource) {
        channel ??= '';
        switch (channelOf) {
            case ChannelOf.Resource: Gi.resChannel = channel; gi.alert(`🎯资源Channel设置为👉👉👉${channel}`); break;
            case ChannelOf.Rms: Gi.rmsChannel = channel; gi.alert(`🎯当前存档用户设置为👉👉👉${channel}`); break;
            case ChannelOf.Language: Gi.langChannel = channel; gi.langTranslate(gi.rootNode); gi.alert(`🎯当前语言设置为👉👉👉${channel}`); break;
        }
    }
    public getChannel(channelOf: ChannelOf = ChannelOf.Resource): string {
        switch (channelOf) {
            case ChannelOf.Resource: return Gi.resChannel;
            case ChannelOf.Rms: return Gi.rmsChannel;
            case ChannelOf.Language: return Gi.langChannel;
        }
    }
    public loadBundle(bundleName: string, callback: (progress: number, path: string, asset: any) => void) {
        let resources = Gi.resources[Gi.resChannel] ??= cc.js.createMap(true);
        gi.alert(`⏳⌛⏳⌛⏳⌛开始加载资源包${Gi.resChannel}${bundleName}⏳⌛⏳⌛⏳⌛`);
        cc.assetManager.loadBundle(`${Gi.resChannel}${bundleName}`, (err: Error, bundle: cc.AssetManager.Bundle) => {
            if (err) { gi.error(`gi.loadBundle加载bundle失败！(${Gi.resChannel}${bundleName})`); return; }
            let files = bundle.getDirWithPath('');
            let cur = 0, total = files.length;
            for (let i = 0; i < total; ++i) {
                let name = files[i].path;
                let type = files[i].ctor.prototype.constructor;
                if (type !== cc.AudioClip && type !== cc.SpriteFrame && type !== cc.JsonAsset
                    && type !== cc.ParticleAsset && type !== cc.Prefab && type !== cc.sp.SkeletonData) {
                    callback(++cur / total, null, null);
                    continue;
                }
                bundle.load(name, type, (err: Error, asset: any) => {
                    let key = `${bundleName}/${type === cc.SpriteFrame && name.endsWith('spriteFrame') ? name.substring(0, name.lastIndexOf('/')) : name}`;
                    if (err) { gi.error(`gi.loadBundle加载资源失败！(${key})`); callback(++cur / total, null, null); return; }
                    // gi.alert(`📁资源加载成功！(${key})`);
                    resources[key] = asset;
                    callback(++cur / total, key, type === cc.JsonAsset ? asset.json : asset);
                });
            }
        });
    }
    public load(path: string): any {
        let resources = Gi.resources[Gi.resChannel];
        if (resources === undefined) { gi.error(`gi.load读取缓存资源失败！(${Gi.resChannel}${path})`); return null; }
        let res = resources[path];
        if (res) return res.constructor === cc.JsonAsset ? res.json : res;
        gi.error(`gi.load读取缓存资源失败！(${Gi.resChannel}${path})`);
        return null;
    }
    public loadAsync(path: string): Promise<any> {
        return new Promise<any>((resolve) => {
            let resources = Gi.resources[Gi.resChannel] ??= cc.js.createMap(true);
            let res = resources[path];
            if (res) { resolve(res.constructor === cc.JsonAsset ? res.json : res); return; }
            let index = path.indexOf('/');
            let bundleName = path.substring(0, index);
            let filePath = path.substring(index + 1);
            cc.assetManager.loadBundle(`${Gi.resChannel}${bundleName}`, (err: Error, bundle: cc.AssetManager.Bundle) => {
                if (err) { gi.error(`gi.loadAsync加载Bundle失败！(${Gi.resChannel}${bundleName})`); resolve(null); return; }
                let files = bundle.getDirWithPath('');
                let type = null;
                for (let i = files.length - 1; i > -1; --i) {
                    let fileType = files[i].ctor.prototype.constructor;
                    if (fileType === cc.SpriteFrame) {
                        if (filePath === files[i].path) { type = fileType; break; }
                        if (filePath + '/spriteFrame' === files[i].path) { filePath += '/spriteFrame'; type = fileType; break; }
                    } else if (fileType === cc.AudioClip || fileType === cc.JsonAsset || fileType === cc.ParticleAsset
                        || fileType === cc.Prefab || fileType === cc.sp.SkeletonData) {
                        if (filePath === files[i].path) { type = fileType; break; }
                    }
                }
                bundle.load(filePath, type, (err: Error, asset: any) => {
                    if (err) { gi.error(`gi.loadAsync加载资源失败！(${Gi.resChannel}${path})`); resolve(null); return; }
                    resources[path] = asset;
                    resolve(asset); return;
                });
            });
        });
    };
    public loadRemoteAsync(url: string, type: any = cc.ImageAsset): Promise<any> {
        return new Promise<any>((resolve) => {
            if (!url) { gi.error(`gi.loadRemoteAsync加载远程资源失败！${url}`); resolve(null); return; }
            let analysis = (type: any, asset: any): any => {
                switch (type) {
                    case cc.ImageAsset:
                    case cc.SpriteFrame:
                        let texture = new cc.Texture2D();
                        texture.image = asset;
                        let frame = new cc.SpriteFrame();
                        frame.texture = texture;
                        return frame;
                    case cc.JsonAsset: return asset.json;
                    default: return asset;
                }
            }
            let resources = Gi.resources[Gi.resChannel] ??= cc.js.createMap(true);
            if (resources[url] !== undefined) { resolve(analysis(type, resources[url])); return; }
            cc.assetManager.loadRemote(url, type, (err: Error, asset: any) => {
                if (err) { gi.error(`gi.loadRemoteAsync加载远程文件失败！(${Gi.resChannel}/${url})`); resolve(null); return; }
                resources[url] = asset;
                resolve(analysis(type, asset)); return;
            });
        });
    };
    public releaseBundle(bundleName: string) {
        let resources = Gi.resources[Gi.resChannel];
        if (resources === undefined) return;
        if (bundleName === 'internal' || bundleName === 'main' || bundleName === 'resources') return;
        let bundle = cc.assetManager.bundles['_map'][`${Gi.resChannel}${bundleName}`];
        if (!bundle) return;
        let paths = bundle._config.paths._map;
        for (let path in paths) {
            path = `${bundleName}/${path}`;
            cc.assetManager.releaseAsset(resources[path]);
            delete resources[path];
        }
        cc.assetManager.removeBundle(bundle);
    }
    public releaseAsset(path?: string) {
        let resources = Gi.resources[Gi.resChannel];
        if (resources === undefined) return;
        if (path === undefined) {
            for (path in resources) { cc.assetManager.releaseAsset(resources[path]); }
            delete Gi.resources[Gi.resChannel];
        } else {
            cc.assetManager.releaseAsset(resources[path]);
            delete resources[path];
        }
    }
    public create(path: string, parent: cc.Node): cc.Node {
        let asset = gi.load(path);
        if (asset === null) { gi.error(`gi.create创建节点失败！(${Gi.resChannel}${path})`); return null; }
        let node: cc.Node = null;
        let nodeName = path.replace(/\//g, '\\');
        switch (asset.constructor) {
            case cc.SpriteFrame:
                node = new cc.Node(nodeName);
                node.setParent(parent);
                node.addComponent(cc.Sprite).spriteFrame = asset;
                break;
            case cc.ParticleAsset:
                node = new cc.Node(nodeName);
                node.setParent(parent);
                node.addComponent(cc.ParticleSystem2D).file = asset;
                break;
            case cc.Prefab:
                node = cc.instantiate(asset);
                node.setParent(parent);
                node.name = nodeName;
                let wgts = node.getComponentsInChildren(cc.Widget);
                for (let i = wgts.length - 1; i > -1; wgts[i--].updateAlignment());
                break;
            case cc.sp.SkeletonData:
                node = new cc.Node(nodeName);
                node.setParent(parent);
                node.addComponent(cc.sp.Skeleton).skeletonData = asset;
                break;
            default: gi.error(`gi.create创建节点失败！(${Gi.resChannel}${path})`); break;
        }
        return node;
    }
    public createAsync(path: string, parent: cc.Node): Promise<cc.Node> {
        return new Promise<cc.Node>(async (resolve) => {
            let asset = await gi.loadAsync(path);
            if (asset === null) { gi.error(`gi.createAsync创建节点失败！(${Gi.resChannel}${path})`); resolve(null); return; }
            let node: cc.Node = null;
            let nodeName = path.replace(/\//g, '\\');
            switch (asset.constructor) {
                case cc.SpriteFrame:
                    node = new cc.Node(nodeName);
                    node.setParent(parent);
                    node.addComponent(cc.Sprite).spriteFrame = asset;
                    break;
                case cc.ParticleAsset:
                    node = new cc.Node(nodeName);
                    node.setParent(parent);
                    node.addComponent(cc.ParticleSystem2D).file = asset;
                    break;
                case cc.Prefab:
                    node = cc.instantiate(asset);
                    node.setParent(parent);
                    node.name = nodeName;
                    let wgts = node.getComponentsInChildren(cc.Widget);
                    for (let i = wgts.length - 1; i > -1; wgts[i--].updateAlignment());
                    break;
                case cc.sp.SkeletonData:
                    node = new cc.Node(nodeName);
                    node.setParent(parent);
                    node.addComponent(cc.sp.Skeleton).skeletonData = asset;
                    break;
                default: gi.error(`gi.create创建节点失败！(${Gi.resChannel}${path})`); break;
            }
            resolve(node); return;
        });
    }
    public poolGet(path: string, parent: cc.Node): cc.Node {
        let node: cc.Node = null;
        let poolLists = Gi.poolLists[Gi.resChannel] ??= cc.js.createMap(true);
        let pool = poolLists[path];
        if (pool?.length > 0) {
            node = pool.pop();
            node.setParent(parent);
        } else {
            node = gi.create(path, parent);
        }
        return node;
    }
    public poolGetAsync(path: string, parent: cc.Node): Promise<cc.Node> {
        return new Promise(async (resolve) => {
            let node: cc.Node = null;
            let poolLists = Gi.poolLists[Gi.resChannel] ??= cc.js.createMap(true);
            let pool = poolLists[path];
            if (pool?.length > 0) {
                node = pool.pop();
                node.setParent(parent);
            } else {
                node = await gi.createAsync(path, parent);
            }
            resolve(node); return;
        });
    }
    public poolPut(node: cc.Node) {
        let poolLists = Gi.poolLists[Gi.resChannel] ??= cc.js.createMap(true);
        let name = node.name.replace(/\\/g, '/');
        poolLists[name] ??= [];
        poolLists[name].push(node);
        node.removeFromParent();
    }
    public poolClear() {
        let poolList = Gi.poolLists[Gi.resChannel];
        if (poolList === undefined) return;
        for (let name in poolList) {
            for (let pool = poolList[name], i = pool.length - 1; i > -1; pool[i--].destroy());
        }
        delete Gi.poolLists[Gi.resChannel];
    }
    public rmsSet(key: string, value: any) {
        if (typeof value === 'function') { gi.error(`gi.rmsSet写入数据失败！${key}：${value}`); return; }
        cc.sys.localStorage.setItem(`${Gi.rmsChannel}${key}`, JSON.stringify(value));
    }
    public rmsGet(key: string): any {
        let value = cc.sys.localStorage.getItem(`${Gi.rmsChannel}${key}`);
        if (value === 'undefined') return undefined;
        return JSON.parse(value);
    }
    public rmsClear(channel?: string) {
        let storage = cc.sys.localStorage;
        if (channel === undefined) {
            cc.sys.localStorage.clear();
        } else {
            for (let key = Object.keys(storage), i = storage.length - 1; i > -1; --i) {
                key[i].startsWith(channel) && storage.removeItem(key[i]);
            }
        }
    }
    public langLoad(langName: string, data: Object) {
        let langs = Gi.languages[Gi.resChannel] ??= cc.js.createMap(true);
        langs[langName] = data;
    }
    public langTranslate(node: cc.Node, langChannel: string = Gi.langChannel, isKeyChanged: boolean = false) {
        let lang = Gi.languages[Gi.resChannel];
        if (!lang) { gi.error(`gi.langTranslate翻译失败！${Gi.resChannel}/${langChannel}`); return; }
        let findKey = (value: string, type: string) => {
            for (let key in lang) {
                let ret = gi.findKey(lang[key][type], value);
                if (ret) return ret;
            }
            return null;
        }
        let data = lang[langChannel].Label;
        if (data) {
            let labels = node.getComponentsInChildren(cc.Label);
            for (let i = labels.length - 1; i > -1; --i) {
                let label = labels[i];
                (label['Key'] === undefined || isKeyChanged) && (label['Key'] = findKey(label.string, 'Label'));
                let str = data[label['Key']];
                str && (label.string = str);
            }
        }
        let resource = Gi.resources[Gi.resChannel];
        if (!resource) { gi.error(`gi.langTranslate翻译失败！${Gi.resChannel}/${langChannel}`); return; }
        data = lang[langChannel].Sprite;
        if (data) {
            let spts = node.getComponentsInChildren(cc.Sprite);
            for (let i = spts.length - 1; i > -1; --i) {
                let spt = spts[i];
                if (!spt.spriteFrame) continue;
                let path = gi.findKey(resource, spt.spriteFrame);
                (spt['Key'] === undefined || isKeyChanged) && (spt['Key'] = findKey(path, 'Sprite'));
                path = data[spt['Key']];
                path && (spt.spriteFrame = gi.load(path));
            }
        }
    }
    public langRelease(langName?: string) {
        let language = Gi.languages[Gi.resChannel];
        if (language === undefined) return;
        langName === undefined ? delete Gi.languages[Gi.resChannel] : delete language[langName];
    }
}
window['gi'] = new Gi();

!EDITOR && cc.director.once(cc.Director.EVENT_AFTER_DRAW, () => {
    gi.rootNode = cc.director.getScene().getComponentInChildren(cc.Canvas).node;
}, this);